<?php

require_once('atom.php');
require_once('db.php');
require_once('guid.php');
require_once('sql.php');
require_once('types.php');
require_once('version.php');
require_once('log.php');

class neon_DbOpts {
    function __construct($dbname, $user = 'neon', 
                         $passwd = '', $server = 'localhost') {
        $this->dbname = $dbname;
        $this->server = $server;
        $this->user   = $user;
        $this->passwd = $passwd;
    }
}

$neon_log = log_Logger();

class neon_API {
    /*
     * Constructor
     */
    function __construct($db_opts) {
        $this->db_opts = $db_opts;
        $this->db_link_ = mysql_connect($db_opts->server, 
                                        $db_opts->user, 
                                        $db_opts->passwd);
        assert($this->db_link_);
        mysql_select_db($db_opts->dbname);
        
        // Get node ID
        $result = mysql_query("SELECT value FROM neon_metadata WHERE name = 'id';");
        assert($result);
        $this->id_ = mysql_fetch_object($result)->value;

        $this->types_ = new types_TypeManager();

        $neon_log->log_info('api', 'new neon_API, node id $this->id_');
    }

    function begin_() {
        $neon_log->log_debug('api', 'BEGIN transaction');
        $r = mysql_query('BEGIN;');
        assert($r);
    }

    function commit_() {
        $neon_log->log_debug('api', 'COMMIT transaction');
        $r = mysql_query('COMMIT;');
        assert($r);
    }


    function integrate_($ty, $vv, $values, $merge_style) {
        // $values table name -> object with the right values
        $guids = array();

        foreach ($ty->tables as $name => $desc) {
            $q = $desc->intersect_query($values[$name]);

            // yields list of guids
            $r = mysql_query($q);
            if (! $r) {
                continue;
            }
            while ($o = mysql_fetch_object($r)) {
                $field = $desc->guid_field;
                $guids[$o->$field] = 1;
            }
        }

        $neon_log->log_debug('api', "integrate: relevant guids:\n%O", $guids);

        foreach ($guids as $g_str => $_) {
            $other_a = $this->get_atom($g_str);
            $neon_log->log_debug('api', "integrate: looking at atom\n%O", $other_a);

            if ($merge_style) {
                // We are merging to form the new value
                $vv->merge($other_a->version());
                $this->del_atom($other_a->guid, false);
            } else {
                // We are doing a comparison to the old value
                switch ($cmp) {
                case '|':
                    // XXX/bowei -- warn about conflict
                    break;
                case '<':
                    break;
                case '>':
                    // XXX/bowei -- this is an error
                    $this->del_atom($g, false);
                    break;
                case '=':
                    // XXX/bowei -- this is an error
                    break;
                }
            }
        }

        // XXX/bowei -- flags
        return array($vv, 0);
    }

    function new_guid_() {
        $this->begin_();
        $r = mysql_query("SELECT value FROM neon_metadata WHERE name = 'next_ver'");
        assert($r);
        $ver = intval(mysql_fetch_object($r)->value);
        $next_ver = $ver + 1;
        $r = mysql_query("UPDATE neon_metadata SET value = '$next_ver' WHERE name = 'next_ver'");
        assert($r);
        $this->commit_();

        return make_guid($this->id_, $ver);
    }

    function add_new_atom_meta_($guid, $vv, $type) {
        $guid_str = guid_to_raw($guid);
        $vv_str = $vv->to_str();
        $sql = "INSERT INTO neon_atom_meta VALUES ('$guid_str', '$vv_str', '$type', 0, 1)";
        $r = mysql_query($sql);
        assert($r);
    }

    /*
     * Public API
     */
    function sql_query($sqlstr) {
        $p = new sql_Parser();
        $r = $p->parse($sqlstr);

        if ($r && $this->types_->handled_table($r->table)) {
            switch ($r->type) {
            case 'insert':
                return $this->insert($r->table, $r->columns, $r->values);
            }
        } else {
            return mysql_query($sqlstr);
        }
    }

    function insert($table, $columns, $values) {
        // XXX/bowei -- this needs to be more sophisticated
        // guid should be the first column and is added to the insert
        // construct values object
        $scm = db_Schema::get($table);
        if (sizeof($columns) == 0) {
            $columns = $scm->column_names();
        } else {
            array_unshift($columns, 'guid');
        }
                
        $guid = $this->new_guid_();
        array_unshift($values, guid_to_raw($guid));
        $vals_obj = (object) array_combine($columns, $values);
        
        $this->begin_();
        $ty = $this->types_->table_type($table);
        assert($ty);
        
        list($vv, $ret) = $this->integrate_($ty, new version_Vector($guid),
                                            array($table => $vals_obj), true);
        $this->add_new_atom_meta_($guid, $vv, $ty->name);
        $sql = sql_format_insert($table, $columns, $values);
        $r = mysql_query($sql);
        assert($r);
        
        $this->commit_();
    }

    function get_atom($guid_str) {
        return new atom_Atom($guid_str);
    }

    function del_atom($guid_str, $tombstone) {
        $a = $this->get_atom($guid_str);
        assert($a);

        $ty = $this->types_->get_type($a->type);
        foreach ($ty->tables as $tabname => $tab) {
            $r = mysql_query("DELETE FROM $tab->name WHERE guid = '$guid_str';");
            assert($r);
        }

        if ($tombstone) {
            $r = mysql_query("UPDATE neon_atom_meta SET deleted = 1 WHERE guid = '$guid_str';");
            assert($r);
        } else {
            $r = mysql_query("DELETE FROM neon_atom_meta WHERE guid = '$guid_str';");
            assert($r);
        }
    }
};

?>